using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using Mud.Common;
using Mud.Common.Threading;
using Mud.Common.Messages;

using Mud.Server.Physical;
using Mud.Server.Users;



namespace Mud.Server
{
	/// <summary>
	/// Main server object.
	/// </summary>
	public class Server : IServer
	{

		/// <summary>
		/// List of clients waiting to be added to the server.
		/// </summary>
		private WaitableList<Client.IClient> _clientsToAdd = new WaitableList<Client.IClient>();

		/// <summary>
		/// List of clients waiting to be removed from the server.
		/// </summary>
		private WaitableList<Client.IClient> _clientsToRemove = new WaitableList<Client.IClient>();

		/// <summary>
		/// List of messages waiting to be broadcasted to clients.
		/// </summary>
		private WaitableList<MessageEventServerBroadcast> _messagesToSend = new WaitableList<MessageEventServerBroadcast>();

		/// <summary>
		/// Areas currently loaded and in use.
		/// </summary>
		private Dictionary<string, Area> _loadedAreas = new Dictionary<string, Area>();

		#region Properties

		/// <summary>
		/// Servers Id manager.
		/// </summary>
		ObjectIdManager IServer.CurrentObjectIdManager
		{
			get { return _objectIdManager; }
		}
		private ObjectIdManager _objectIdManager = new ObjectIdManager();

		/// <summary>
		/// Gets the command factory for creating command message handlers.
		/// </summary>
		MessageHandlerFactory<Client.Commands.ICommand> IServer.CurrentCommandFactory
		{
			get { return _commandFactory; }
		}
		private MessageHandlerFactory<Client.Commands.ICommand> _commandFactory;

		/// <summary>
		/// Current server state.
		/// </summary>
		ServerState IServer.CurrentServerState
		{
			get { 
				return _serverState; 
			}
		}
		private ServerState _serverState = new ServerState();

		/// <summary>
		/// Server configuration
		/// </summary>
		public ServerConfiguration CurrentConfiguration
		{
			get { return _configuration; }
		}
		private ServerConfiguration _configuration;


		/// <summary>
		/// Object loader.
		/// </summary>
		ObjectLoader IServer.CurrentObjectLoader
		{
			get { return _loader; }
		}
		private ObjectLoader _loader;


		/// <summary>
		/// List of server clients which will be processed for commands and events.
		/// </summary>
		IReadOnlyCollection<Client.IClient> IServer.Clients
		{
			get { return _serverClients; }
		}
		private ReadOnlyCollection<Client.IClient> _serverClients = new ReadOnlyCollection<Client.IClient>();


		/// <summary>
		/// This list contains methods with specific time index (miliseconds).
		/// When the server reaches this index (tick) it will execute the method.
		/// </summary>
		Scheduler IServer.CurrentScheduler
		{
			get { return _scheduler; }
		}
		private Scheduler _scheduler = new Scheduler();

		/// <summary>
		/// Current time index (milliseconds) that the server is in. This is used primarily
		/// for CommandScheduler.
		/// </summary>
		/// <value></value>
		int IServer.CurrentTimeIndex
		{
			get { return _time; }
		}
		private int _time;

		/// <summary>
		/// StopFlag which can be used to stop the server.
		/// </summary>
		StopFlag IServer.ServerStopFlag
		{
			get { return _stopFlag; }
		}
		private StopFlag _stopFlag = new StopFlag();


		#endregion

		#region Server engine core

		/// <summary>
		/// Starts the server.
		/// </summary>
		public void Start()
		{

			int preferedTickDuration = _configuration.TickDuration;

            Logger.Log(LogType.Info, "Server.Start", " ");
            Logger.Log(LogType.Info, "Server.Start", " ");
            Logger.Log(LogType.Info, "Server.Start", "--------------------------------------------------------");
            Logger.Log(LogType.Info, "Server.Start", "Server '{0}' starting.", _configuration.ServerName);
            Logger.Log(LogType.Info, "Server.Start", "Server cycle every {0} seconds.", (float)_configuration.TickDuration / 1000);
            Logger.Log(LogType.Info, "Server.Start", "--------------------------------------------------------");

			_loader = new ObjectLoader(this);
			_serverState.ServerStarted = DateTime.Now;
			_commandFactory = new MessageHandlerFactory<Client.Commands.ICommand>();

			_time = Environment.TickCount;

			TimeFormater.StartingTimeIndex = _time;

            Logger.Log(LogType.Info, "Server.Start", "Server '{0}' started.", _configuration.ServerName);

			// main loop
			while (!_stopFlag.IsSet)
			{
				int tickStartTick = Environment.TickCount;
				_time = tickStartTick;

				Tick();

				int tickEndTick = Environment.TickCount;
				_serverState.LastTickDuration = tickEndTick - tickStartTick;
				_serverState.ServerLag = (float)_serverState.LastTickDuration / preferedTickDuration;

				if (_serverState.ServerLag > 0.5f)
                    Logger.Log(LogType.Warning, "Server.Start", "Server lag: {0} ({1}/{2})", _serverState.ServerLag, _serverState.LastTickDuration, preferedTickDuration);

				if (_serverState.LastTickDuration < preferedTickDuration)
					Thread.Sleep(preferedTickDuration - _serverState.LastTickDuration);

			}

			if (ServerStopping != null)
				ServerStopping(this, EventArgs.Empty);

			foreach (Client.IClient client in _serverClients)
				client.DisconnectClient();

			Tick();

			_configuration.SaveConfiguration();
            Logger.Log(LogType.Info, "Server.Start", "Server '{0}' ended.", _configuration.ServerName);
		}

		public void Stop()
		{
            Logger.Log(LogType.Info, "Server.Stop", "Server '{0}' will be stopped.", _configuration.ServerName);
            _stopFlag.SetFlag(true);
		}

		/// <summary>
		/// Every few milliseconds this method is run. The main processing is done inside this function.
		/// </summary>
		protected virtual void Tick()
		{
			CheckClients();
			RemoveDeadClients();
			CheckBroadcastMessages();
			ProcessClientCommands();
			_scheduler.ExecuteScheduledMethods(this._time);
			ProcessClientEvents();
		}


		private void CheckClients()
		{
			if (_clientsToAdd.ItemExists > 0)
			{
				lock (_clientsToAdd)
				{
					foreach (Client.IClient client in _clientsToAdd)
					{
						_serverClients.Add(client);
						if (client is Client.RemoteServerClient)
							_serverState.RemoteClientCount++;
						_serverState.ClientCount++;
                        Logger.Log(LogType.Debug, "Server.CheckClients", "New client {0} (user {1}) has been added to the server.", client.ObjectId, client.User.UserName);
						if (ClientConnected != null)
						{
							ClientConnectedEventArgs args = new ClientConnectedEventArgs(client);
							ClientConnected(this, args);
						}
					}
					_clientsToAdd.Clear();
				}
			}
			if (_clientsToRemove.ItemExists > 0)
			{
				lock (_clientsToRemove)
				{
					foreach (Client.IClient client in _clientsToRemove)
					{
						client.Status = Client.ClientStatus.Disconnected;
					}
					_clientsToRemove.Clear();
				}
			}

			// check for disconnected clients and inform other clients
			foreach (Client.IClient client in _serverClients)
			{
				if (client.Status == Client.ClientStatus.Disconnected)
				{
					if (ClientDisconnected != null)
					{
						ClientDisconnectedEventArgs args = new ClientDisconnectedEventArgs(client);
						ClientDisconnected(this, args);
					}
					client.DisconnectClient();
				}
			}
		}

		private void CheckBroadcastMessages()
		{
			if (_messagesToSend.ItemExists > 0)
			{
				lock (_messagesToSend)
				{
					foreach(MessageEventServerBroadcast msg in _messagesToSend)
						foreach (Client.IClient client in _serverClients)
							client.EventQueue.Enqueue(msg);
				}
			}
		}

		private void ProcessClientCommands()
		{
			_serverState.CommandMessagesProcessedLastTick = 0;
			foreach (Client.IClient client in _serverClients)
			{
				if (client.ProcessCommand())
				{
					_serverState.CommandMessagesProcessedLastTick++;
					_serverState.CommandMessagesProcessedTotal++;
				}
			}
		}

		private void ProcessClientEvents()
		{
			_serverState.EventMessagesProcessedLastTick = 0;
			foreach (Client.IClient client in _serverClients)
			{
				int eventCount = client.ProcessEventQueue();
				_serverState.EventMessagesProcessedLastTick += eventCount;
				_serverState.EventMessagesProcessedTotal += eventCount;
			}
		}


		private void RemoveDeadClients()
		{
			for (int i = 0; i < _serverClients.Count; i++)
			{
				if (_serverClients[i].Status != Client.ClientStatus.Ready)
				{

					if (_serverClients[i] is Client.RemoteServerClient)
						_serverState.RemoteClientCount--;
					_serverState.ClientCount--;

                    Logger.Log(LogType.Debug, "Server.RemoveDeadClients", "Client {0} (user {1}) is beeing removed from the server.", _serverClients[i].ObjectId, _serverClients[i].User.UserName);
					_serverClients.RemoveAt(i);
					i = 0;
				}
			}
		}


		#endregion

		#region Events

		/// <summary>
		/// Event raised when client connects.
		/// </summary>
		public event EventHandler<ClientConnectedEventArgs> ClientConnected;

		/// <summary>
		/// Event raised when client disconnects.
		/// </summary>
		public event EventHandler<ClientDisconnectedEventArgs> ClientDisconnected;

		/// <summary>
		/// Event raised when a player characters enters the game.
		/// </summary>
		public event EventHandler<PlayerCharacterEnteredGameEventArgs> PlayerCharacterEnteredGame;

		/// <summary>
		/// Event raised when a player characters exits the game.
		/// </summary>
		public event EventHandler<PlayerCharacterExitedGameEventArgs> PlayerCharacterExitedGame;

		/// <summary>
		/// Event raised when the server is stoping.
		/// </summary>
		public event EventHandler ServerStopping;

		#endregion

		public Server()
		{
			_configuration = ServerConfiguration.LoadConfiguration();
		}

		#region Server commands

		/// <summary>
		/// Sends a message to all clients.
		/// </summary>
		/// <param name="sender">Message sender.</param>
		/// <param name="message">Message to send.</param>
		public ResultWithReason BroadcastMessage(string sender, string message)
		{
			MessageEventServerBroadcast msg = new MessageEventServerBroadcast();
			msg.SenderName = sender;
			msg.Message = message;
			lock (this)
			{
				_messagesToSend.Add(msg);
			}
			return ResultWithReason.Ok;
		}



		/// <summary>
		/// Places the character in the game world. If this method fails for any reason
		/// then the returning message will have description why it has failed.
		/// </summary>
		/// <param name="creature">The character to place.</param>
		/// <param name="areaName">Name of the area where the character is located.</param>
		ResultWithReason IServer.EnterGame(Creature creature, string areaName)
		{
			Area area = ((IServer)this).GetArea(areaName);
			ResultWithReason response = area.EnterArea(creature, creature.Location);
			if (response.Success)
				if (PlayerCharacterEnteredGame != null)
					PlayerCharacterEnteredGame(this, new PlayerCharacterEnteredGameEventArgs(creature));
			return response;
		}

		/// <summary>
		/// Removes the character in the game world. If this method fails for any reason
		/// then the returning message will have description why it has failed.
		/// </summary>
		/// <param name="creature">The character to remove.</param>
		ResultWithReason IServer.ExitGame(Creature creature)
		{
			Area area = creature.CurrentArea;
			ResultWithReason response = ResultWithReason.Ok;
			if (area != null)
			{
				response = area.ExitArea(creature);
			}
			if (response.Success)
				if (PlayerCharacterExitedGame != null)
					PlayerCharacterExitedGame(this, new PlayerCharacterExitedGameEventArgs(creature));
			return response;
		}


		/// <summary>
		/// Gets the area with specific name.
		/// </summary>
		/// <param name="areaName">Area name.</param>
		/// <returns>Area.</returns>
		Area IServer.GetArea(string areaName)
		{
			Area area;
			if (!_loadedAreas.TryGetValue(areaName, out area))
			{
				area = _loader.LoadArea(areaName);
				_loadedAreas[areaName] = area;
			}
			return area;
		}


		/// <summary>
		/// Adds a new client to the server.
		/// This method is tread safe.
		/// </summary>
		/// <param name="client">Client to add.</param>
		/// <returns></returns>
		public ResultWithReason AddClient(Client.IClient client)
		{
			lock (_clientsToAdd)
			{
				if(!_clientsToAdd.Contains(client))
					_clientsToAdd.Add(client);
			}
			return ResultWithReason.Ok;
		}

		public ResultWithReason RequestRemoveClient(Mud.Server.Client.IClient client)
		{
			lock (_clientsToAdd)
			{
				if(!_clientsToRemove.Contains(client))
					_clientsToRemove.Add(client);
			}
			return ResultWithReason.Ok;
		}

		#endregion

	}

}
